[id].vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <template>
  2. <!-- 分类 -->
  3. <div class="main-background">
  4. <!-- SEO -->
  5. <Head>
  6. <Title>厦门市文化遗产保护中心 - {{ channelName }}</Title>
  7. <Meta name="description" content="" />
  8. <Meta name="keywords" content="" />
  9. </Head>
  10. <!-- 轮播 -->
  11. <Carousel v-bind="carouselConfig" class="main-header-image carousel-light">
  12. <Slide
  13. v-for="(item, key) in carouselData.content.value"
  14. :key="key"
  15. class="main-header-image"
  16. >
  17. <img class="main-header-image" :src="item.image" />
  18. </Slide>
  19. <template #addons>
  20. <Navigation />
  21. <Pagination />
  22. </template>
  23. </Carousel>
  24. <!-- 主要内容 -->
  25. <div class="main-content">
  26. <div class="container">
  27. <div class="row">
  28. <!-- 左侧导航 -->
  29. <div class="col-12 col-sm-12 col-md-4 col-lg-3">
  30. <div class="sidebar">
  31. <div class="title">
  32. <h2>{{ channelName }}</h2>
  33. </div>
  34. <ul class="sidebar-menu">
  35. <li v-for="(item, key) in channelData.content.value?.childs" :key="key">
  36. <a v-if="item.type === 'link'" :href="item.outlink" target="_blank">{{ item.name }}</a>
  37. <router-link v-else :to="`/channel/${item.id}`" :class="{ 'active': item.id == channelId }">
  38. {{ item.name }}
  39. <Icon name="material-symbols-light:chevron-right" />
  40. </router-link>
  41. </li>
  42. <li v-if="channelData.content.value?.parent_id !== 0">
  43. <router-link :to="`/channel/${channelData.content.value?.parent_id}`">
  44. <div>
  45. <Icon name="material-symbols:undo" />
  46. 返回上一级
  47. </div>
  48. </router-link>
  49. </li>
  50. <li v-if="!channelData.content.value?.childs || channelData.content.value?.childs.length === 0" class="no-content">暂无相关子分类</li>
  51. </ul>
  52. </div>
  53. </div>
  54. <!-- 右侧内容 -->
  55. <div class="col-12 col-sm-12 col-md-8 col-lg-9">
  56. <div class="content">
  57. <div class="section-title">
  58. <h2 class="icon">{{ channelName }}</h2>
  59. <nav aria-label="breadcrumb">
  60. <ol class="breadcrumb">
  61. <li class="breadcrumb-item"><router-link to="/">首页</router-link></li>
  62. <li v-for="(item, key) in channelData.content.value?.parents" :key="key" class="breadcrumb-item">
  63. <a v-if="item.type === 'link'" :href="item.outlink" target="_blank">{{ item.name }}</a>
  64. <router-link v-else :to="`/channel/${item.id}`">{{ item.name }}</router-link>
  65. </li>
  66. <li class="breadcrumb-item active" aria-current="page">{{ channelName }}</li>
  67. </ol>
  68. </nav>
  69. </div>
  70. <!-- 文章列表 -->
  71. <SimplePageContentLoader :loader="articlesData">
  72. <div class="news-list">
  73. <div v-for="(item, key) in articlesData.content.value?.items" :key="key" class="news-item">
  74. <router-link :to="'/page/' + item.id" class="title">{{ item.title }}</router-link>
  75. <span class="date">{{ DateUtils.formatDate(new Date((item.createtime ||item.publishtime) * 1000), 'yyyy-MM-dd') }}</span>
  76. </div>
  77. <div v-if="!articlesData.content.value || articlesData.content.value.empty" class="no-news">暂无相关文章</div>
  78. </div>
  79. <!-- 分页 -->
  80. <SimplePagination
  81. v-if="articlesData.content.value"
  82. :currPage="articlesData.content.value.pageIndex"
  83. :allPage="articlesData.content.value.allPage"
  84. :maxCount="10"
  85. />
  86. </SimplePageContentLoader>
  87. </div>
  88. </div>
  89. </div>
  90. </div>
  91. </div>
  92. </div>
  93. </template>
  94. <script setup lang="ts">
  95. import { computed } from 'vue';
  96. import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'
  97. import { useSSrSimpleDataLoader } from '@/composeable/SimpleDataLoader';
  98. import { DateUtils } from '@imengyu/imengyu-utils';
  99. import type { IChannel } from '~~/server/api/channel/[id]';
  100. import SimplePagination from '~/components/content/SimplePagination.vue';
  101. const carouselConfig : (typeof Carousel['props']) = {
  102. itemsToShow: 1,
  103. wrapAround: true,
  104. autoplay: 5000,
  105. }
  106. const route = useRoute();
  107. const channelId = parseInt(route.params.id as string);
  108. const channelData = await useSSrSimpleDataLoader('channel' + channelId, async () => {
  109. const res = await $fetch(`/api/channel/${channelId}`);
  110. if (!res.status)
  111. throw new Error(res.message);
  112. return res.data as IChannel & {
  113. childs: IChannel[];
  114. parents: IChannel[];
  115. };
  116. });
  117. const carouselData = await useSSrSimpleDataLoader('carousel' + channelId, async () => {
  118. const res = await $fetch(`/api/carousel`);
  119. if (!res.status)
  120. throw new Error(res.message);
  121. return res.data;
  122. });
  123. const articlesData = await useSSrSimpleDataLoader('articles' + channelId, async () => {
  124. const res = await $fetch('/api/article/byChannel', {
  125. method: 'GET',
  126. query: {
  127. channelId: channelId,
  128. page: route.query.page || 1,
  129. pageSize: 10,
  130. }
  131. });
  132. if (!res.status)
  133. throw new Error(res.message);
  134. if (res.data?.empty && channelData.content.value?.childs?.[0]) {
  135. // 没有文章时,并且有子分类时,尝试读取全部第一级子分类的文章
  136. const res = await $fetch('/api/article/byChannelAndOnLevelChild', {
  137. method: 'GET',
  138. query: {
  139. channelId: channelId,
  140. page: route.query.page || 1,
  141. pageSize: 10,
  142. }
  143. });
  144. if (!res.status)
  145. throw new Error(res.message);
  146. return res.data;
  147. }
  148. return res.data;
  149. });
  150. const channelName = computed(() => channelData.content.value?.name || '');
  151. watch(() => route.query.page, async (newVal, oldVal) => {
  152. if (newVal !== oldVal) {
  153. articlesData.loadData(undefined, true);
  154. }
  155. })
  156. </script>
  157. <style lang="scss">
  158. @use "sass:list";
  159. @use "sass:math";
  160. </style>